/*
 * File:   mh.c
 * Author: Samik Saha (samiksaha88@gmail.com)
 * Description: This program implements the Mobile Host. It does mainly
 *              three tasks - compute, send and receive. First a TCP
 *              connection to its home station is established. Then it
 *              executes its tasks sequentially as listed in
 *              the events file.
 * Compiler: gcc
 *
 * Created on 25 November, 2008, 8:23 PM
 */

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include "mh.h"
#include "simulator.h"

int msqid; //Message Queue Id
Mobile_host mh; //represents the current mobile host

/*
 * Main Function
 */
int main(int argc, char** argv) {
  FILE *fp; //File pointer for events file
  char file[200] = "data/events.mh"; //common part of the file names
  char task[15]; //variable for holding current event
  int h; //sending or receiving host
  int seq_no[TOTAL_NO_MHS], ack_seq_no, exp_seq_no[TOTAL_NO_MHS];
  int i;
  int mobility = 0;
  char outp[100];

  int sd, rc;

  int CONNECTED = 0;

  /* Strings for storing messages */
  char text[MSGSZ];
  Message m;

  /* Variables used in Message Queue */
  int msgflg = IPC_CREAT | 0666;
  key_t mq_key; //Unique key for the Message Queue
  Message sbuf; //Buffer for storing messages

  pthread_t thread;

  /* Get the events from file */
  strcat(file, argv[1]); //Generate filename for this MH
  fp = fopen(file, "r"); //Open the event list file

  mh.id = atoi(argv[1]); //Get the id of this MH from argument
  mh.hs = atoi(argv[2]); //Get home station

  /* Create a Message Queue */
  mq_key = 1111 + mh.id; //Generate a unique key
  if ((msqid = msgget(mq_key, msgflg)) < 0) {
    perror("msgget");
    exit(1);
  }

  sprintf(outp, "MH %d started", mh.id);
  output(outp, INFO);

  /* Initialise sequence nos */
  for (i = 0; i < TOTAL_NO_MHS; i++) {
    seq_no[i] = 0;
    exp_seq_no[i] = 0;
  }
  while (fscanf(fp, "%s", task) != EOF) {//scan a event from file

    if (!CONNECTED) {//connecting for the first time or connection lost
      /* Connect to Home Station */
      if ((sd = connect_to_hs()) == -1)
        exit(EXIT_FAILURE);
      else {
        sprintf(outp, "Connected to MSS %d (new HS)", mh.hs);
        output(outp, IMP_INFO);
      }
      /* Start receiving thread */
      if (pthread_create(&thread, NULL, receive, sd) != 0)
        exit(EXIT_FAILURE);
      CONNECTED = 1; //set connection ststus to true
    }//end if(NOT-CONNECTED)

    /* Start working */

    if (strcmp(task, "compute") == 0) compute(mh.id); //------COMPUTE------

    else if (strcmp(task, "send") == 0) {//-----------SEND---------------------
      fscanf(fp, "%d", &h); //Whom to send
      fscanf(fp, "%s", text); //what to send

      sprintf(outp, "MH %d: Send to MH-%d:", mh.id, h);
      output(outp, MSG_SENT);

      /* construct data msg */
      m.msg_type = htonl(COMPT_MSG);
      m.sender_id = htonl(mh.id);
      m.dest_id = htonl(h);
      m.seq_no = htonl(seq_no[h]);
      strcpy(m.content, "DUMMY-MSG");

      /* send msg to hs */
      rc = send(sd, &m, sizeof (m), 0);
      if (rc < 0) {//error checking
        sprintf(outp, "CANNOT SEND DATA\n");
        output(outp, ERROR);
        close(sd);
        exit(1);
      }

      sprintf(outp, "      data sent - (%s)", m.content);
      output(outp, MSG_SENT);

      sprintf(outp, "      waiting for ack...");
      output(outp, MSG_SENT);

      do {//Wait for Correct Ack
        dequeue_message(msqid, 100 + h, &sbuf);
        ack_seq_no = ntohl(sbuf.seq_no);
      } while (ack_seq_no != seq_no[h]);

      sprintf(outp, "      received ack");
      output(outp, MSG_SENT);

      seq_no[h] = (seq_no[h] + 1) % 100; //increment seq no

      //sleep(1); //delay 1 sec (required to separate consecutive sent msgs)
    } else if (strcmp(task, "receive") == 0) {//-------------RECEIVE-----------
      fscanf(fp, "%d", &h); //From whom

      sprintf(outp, "MH %d: Receive from MH-%d: ...", mh.id, h);
      output(outp, MSG_RCVD);

      do {//wait for correct message
        /* Read from message queue */
        dequeue_message(msqid, h, &sbuf);
        ack_seq_no = ntohl(sbuf.seq_no);
      } while (ack_seq_no != exp_seq_no[h]);

      sprintf(outp, "      received message - (%s)", sbuf.content);
      output(outp, MSG_RCVD);

      /* Send Acknowledgement */
      m.msg_type = htonl(ACK_MSG);
      m.sender_id = htonl(mh.id);
      m.dest_id = htonl(h);
      m.seq_no = sbuf.seq_no;
      strcpy(m.content, "ACK");
      if (send(sd, &m, sizeof (m), 0)) {//send ack
        sprintf(outp, "      sent ack");
        output(outp, MSG_RCVD);
      } else {
        sprintf(outp, "ERROR");
        output(outp, ERROR);
      }

      exp_seq_no[h] = (exp_seq_no[h] + 1) % 100; //increment expected seq no

      //sleep(1);
    }//end if(RECEIVE)

    /* Simulate Mobility - disconnect occassionally*/
    //mobility = rand() % 10;

    if (mobility >= 6) {
      /* Send Disconn. msg to HS */
      m.msg_type = htonl(MH_DISCONNECT);
      m.sender_id = htonl(mh.id);

      rc = send(sd, &m, sizeof (m), 0); //send message
      if (rc < 0) {//error checking
        sprintf(outp, "CANNOT SEND DISCONN. MSG", mh.id);
        output(outp, ERROR);
        close(sd);
        exit(1);
      }
      pthread_join(thread, NULL);
      shutdown(sd, 2);
      close(sd);
      sprintf(outp, "Lost connection with MSS-%d (HS)", mh.hs);
      output(outp, ERROR);

      mh.hs = (mh.hs == 0) ? 1 : 0; //change Home Station

      CONNECTED = 0; //set conection status to FALSE

    }//end if
  }//end while

  if (CONNECTED) {//if connection is not lost automatically (mobility)
    /* Send disconnection msg to HS */
    m.msg_type = htonl(MH_DISCONNECT);
    m.sender_id = htonl(mh.id);
    strcpy(m.content,"MH DISCONNECT");

    rc = send(sd, &m, sizeof (m), 0); //send message
    if (rc < 0) {//error checking
      sprintf(outp, "CANNOT SEND DATA", mh.id);
      output(outp, ERROR);
      close(sd);
      exit(1);
    }
    /* Wait for receving thread to terminate */
    pthread_join(thread, NULL);
    sprintf(outp, "Disconnected from MSS %d", mh.hs);
    output(outp, INFO);
    close(sd);
  }

  sprintf(outp, "Finished execution");
  output(outp, IMP_INFO);
  fclose(fp);
  getchar();
  return (EXIT_SUCCESS);
}

/*
 * Function: compute()
 * This function simulates computing by keeping the CPU busy for some time
 */
void compute(int mh_no) {
  char outp[100];
  time_t start_time = time(NULL);
  sprintf(outp, "MH %d: Computing...", mh_no);
  output(outp, COMPUTING);
  while (time(NULL) == start_time) {
    // do nothing except chew CPU slices for up to one second.
  }
}

/*
 * Function: receive_msg()
 * This function is executed as a separate thread to receive msgs
 * indepedently and write them to the pipe.
 */
static void *receive(int sd) {
  Message msg;
  int msg_type, sender_id;

  while (1) {
    if (receive_message(sd, &msg) == -1) {//receive msg from socket
      break;
    }
    msg_type = ntohl(msg.msg_type);
    sender_id=ntohl(msg.sender_id);

    switch (msg_type) {
      case CHKPNT_RQST://received checkpoint request msg
        printf("Taking Checkpoint\n");
        /* ---yet to implement---- */
        break;
      case COMPT_MSG:
        msg.qmtype=sender_id;
        enqueue_message(msqid, &msg); //put the message in Message Queue
        break;
      case ACK_MSG:
        msg.qmtype = 100+sender_id;
        enqueue_message(msqid, &msg); //put the message in Message Queue
        break;
      default:
        printf("RECEIVED UNKNOWN MSG TYPE\n");
        break;
    }//end switch
  }//end while
  return NULL;
}//end receive_msg()

int connect_to_hs() {
  /* Variables required for socket connection */
  int sd, rc;
  struct sockaddr_in servAddr;

  Message msg;

  /* build server address structure */
  bzero((char *) & servAddr, sizeof (servAddr));
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  servAddr.sin_port = htons(49152 + mh.hs);

  /* create stream socket */
  sd = socket(AF_INET, SOCK_STREAM, 0);
  if (sd < 0) {
    printf("MH %d: CANNOT CREATE STREAM SOCKET.\n", mh.id);
    return -1;
  }

  /* connect to server */
  rc = connect(sd, (struct sockaddr *) & servAddr, sizeof (servAddr));
  if (rc < 0) {
    printf("MH %d: CANNOT CONNECT TO MSS-%d\n", mh.id, mh.hs);
    return -1;
  }

  /* Send an initialisation msg to HS */
  msg.msg_type = htonl(MH_CONNECT);
  msg.sender_id = htonl(mh.id);
  msg.dest_id = htonl(mh.hs);
  strcpy(msg.content,"MH CONNECT");
          
  rc = send(sd, &msg, sizeof (msg), 0); //send message
  if (rc < 0) { //error checking
    printf("CANNOT SEND DATA\n", mh.id);
    close(sd);
    return -1;
  }
  sleep(1);
  return sd;
}
